package com.gxy.learn.securityauth.auth.filter;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxy.learn.securityauth.common.Result;
import com.gxy.learn.securityauth.config.JwtProperties;
import com.gxy.learn.securityauth.jwt.JwtUtil;
import com.gxy.learn.securityauth.jwt.JwtUser;
import com.gxy.learn.securityauth.jwt.JwtVersionUtil;
import com.gxy.learn.securityauth.vo.LoginUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Description 登录验证
 * <p>
 * UsernamePasswordAuthenticationToken继承了AbstractAuthenticationToken抽象类
 * 其主要与AbstractAuthenticationToken的区分就是针对使用用户名和密码验证的请求按照约定进行了一定的封装：将username赋值到了principal ，而将password赋值到了
 * @author: Gao xueyong
 * Create at: 2021/12/9 下午13:47
 */
@Slf4j
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Autowired
    private CacheManager cacheManager;
    @Autowired
    private JwtVersionUtil jwtVersionUtil;

    /**
     * UsernamePasswordAuthenticationToken继承了AbstractAuthenticationToken抽象类 强制的只对POST请求应用 我们这里修改下 只对登录请求强制使用post
     */
    public LoginAuthenticationFilter(String defaultLoginurl) {
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultLoginurl, HttpMethod.POST.name()));

    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        LoginUserInfo user = new LoginUserInfo();
        try {
            user = new ObjectMapper().readValue(request.getInputStream(), LoginUserInfo.class);
        } catch (IOException e) {
            log.error("解析登录请求失败!", e);
        }
        /**
         * 权限信息
         */
        List<GrantedAuthority> authorities = new ArrayList<>();

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), authorities);
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * 验证通过
     *
     * @param request
     * @param response
     * @param chain
     * @param authentication
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        JwtUser jwtUser = (JwtUser) authentication.getPrincipal();

        //将生成的authentication放入容器中，生成安全的上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSONObject.toJSONString(Result.success("登录成功")));

        Cache cache = cacheManager.getCache(JwtUtil.CAPTCHA_CACHE_KEY);
        //生成token版本号
        String versionKey = JwtUtil.loginVersionKey(jwtUser.getUsername());
        Integer loginTokenVersion = jwtVersionUtil.loginVersion(jwtUser.getUsername());
        //            版本号放入缓存
        cache.put(versionKey, loginTokenVersion);
        //生成token
        final String realToken = JwtUtil.sign(jwtUser.getUsername(), jwtUser.getUsername(),loginTokenVersion);
        response.setHeader(JwtProperties.getHeader(), realToken);
    }

    /**
     * 登录失败！
     *
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        log.error("用户名密码错误");
        // 指定响应格式是json
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSONObject.toJSONString(Result.error("用户名密码错误")));
    }

    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }
}
